ES 모듈을 사용한 Node.js 프로젝트 현대화
Olivia Novak
Dev Intern · Leapcell

소개
수년간 Node.js 개발자들은 주로 CommonJS를 모듈 관리로 사용해 왔으며, 코드를 구성하고 공유하기 위해 require()와 module.exports를 사용했습니다. 이 시스템은 잘 작동했으며 견고한 생태계를 조성했습니다. 그러나 JavaScript 자체에서 ECMAScript Modules(ESM)가 표준화되면서 피할 수 없는 변화가 시작되었습니다. ESM은 정적 분석 이점, 더 나은 트리 쉐이킹 기능, Node.js를 브라우저 측 모듈 로딩과 일치시켜 보다 통일된 JavaScript 개발의 길을 열어줍니다. 많은 최신 라이브러리와 프레임워크가 이제 기본적으로 ESM을 채택함에 따라 CommonJS에 머물러 있는 프로젝트는 다소 뒤떨어지거나 호환성 문제에 직면하게 됩니다. 이 발전은 많은 기존 애플리케이션에 중요한 질문을 제기합니다: 기존 CommonJS Node.js 프로젝트를 미래 지향적인 ES 모듈로 전환하는 방법은 무엇일까요? 해답은 종종 package.json 파일의 간단하지만 강력한 구성인 "type": "module"에 있습니다. 이 문서는 이 마이그레이션을 이해하고 실행하는 과정을 안내하여 Node.js 프로젝트가 최신 모듈 시스템을 채택하도록 보장합니다.
모듈 전환 이해하기
마이그레이션에 들어가기 전에 이 논의의 핵심이 되는 몇 가지 기본 개념을 명확히 해보겠습니다.
핵심 용어
-
CommonJS (CJS): Node.js의 원래이자 기본 모듈 시스템입니다.
require()를 사용하여 모듈을 가져오고module.exports또는exports를 사용하여 노출합니다. 동기식이므로 모듈이 하나씩 로드됩니다.// CommonJS 예제 const express = require('express'); function greet(name) { return `Hello, ${name}!`; } module.exports = greet; -
ECMAScript Modules (ESM): 브라우저와 Node.js에서 기본적으로 지원되는 JavaScript의 공식 모듈 표준입니다.
import및export문을 사용합니다. ESM은 비동기식으로 설계되어 더 나은 성능 최적화를 가능하게 합니다.// ESM 예제 import express from 'express'; export function greet(name) { return `Hello, ${name}!`; } -
package.jsontype필드: Node.js 13.2에서 도입된 이 필드를 통해 개발자는 패키지 내의 모든.js파일에 대한 모듈 시스템을 지정할 수 있습니다."type": "commonjs": (지정되지 않은 경우 기본값) 모든.js파일은 CommonJS로 처리됩니다."type": "module": 모든.js파일은 ES 모듈로 처리됩니다.
"type": "module" 메커니즘
package.json에 "type": "module"를 설정하면 Node.js는 해당 패키지 내의 .js 파일에 대한 기본 파서를 변경합니다. CommonJS로 해석하는 대신 이제 ES 모듈로 처리합니다. 이는 여러 연쇄 효과를 가져옵니다.
- 기본 모듈 시스템 역전: 프로젝트(및 하위 디렉토리)의 모든
.js파일에서require()또는module.exports를 사용하려고 하면 이제 오류가 발생하며import및export구문이 필요합니다. - 파일 확장자 재정의:
.cjs또는.mjs파일 확장을 명시적으로 사용하여 ESM 프로젝트 내(또는 CommonJS 프로젝트 내의 ESM 파일)에서 CommonJS 파일을 계속 사용할 수 있습니다..cjs파일은package.json의type필드에 관계없이 항상 CommonJS로 처리됩니다..mjs파일은package.json의type필드에 관계없이 항상 ES 모듈로 처리됩니다. 이를 통해 마이그레이션 중에 점진적인 전환을 허용하는 유연성을 제공합니다.
실용적인 마이그레이션 단계
간단한 CommonJS Node.js 프로젝트를 ES 모듈로 마이그레이션하는 과정을 살펴보겠습니다.
초기 CommonJS 프로젝트 구조
app.js와 유틸리티 모듈 utils.js가 있는 프로젝트를 고려해 보세요.
package.json (초기):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js" } }
utils.js (CommonJS):
// utils.js function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } module.exports = { add, subtract };
app.js (CommonJS):
// app.js const math = require('./utils'); const express = require('express'); // express가 설치되었다고 가정 const app = express(); const port = 3000; console.log('Addition:', math.add(5, 3)); // 출력: Addition: 8 console.log('Subtraction:', math.subtract(10, 4)); // 출력: Subtraction: 6 app.get('/', (req, res) => { res.send('Hello from CommonJS app!'); }); app.listen(port, () => { console.log(`CommonJS app listening at http://localhost:${port}`); });
이를 실행하려면 express가 설치되었는지 확인하고(npm i express) npm start를 실행하세요.
1단계: package.json 업데이트
가장 중요하고 첫 번째 단계는 package.json에 "type": "module"을 추가하는 것입니다.
package.json (수정됨):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "type": "module", <-- 이 줄을 추가하세요 "scripts": { "start": "node app.js" } }
이제 npm start를 시도하면 Node.js가 이전 CommonJS 파일을 ES 모듈로 구문 분석하려고 하므로 SyntaxError: Unexpected token 'export' 또는 ReferenceError: require is not defined와 같은 오류가 발생할 가능성이 높습니다.
2단계: CommonJS 구문을 ES 모듈 구문으로 변환
CommonJS 파일에서 require()/module.exports를 import/export로 변환하기 위해 이동해야 합니다.
utils.js (ESM 변환됨):
// utils.js export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; }
참고: module.exports = { add, subtract }의 경우 export default { add, subtract }를 사용하고 app.js에서 import math from './utils.js'를 가져올 수도 있습니다. 그러나 명명된 내보내기가 일반적으로 선호됩니다.
app.js (ESM 변환됨):
// app.js import { add, subtract } from './utils.js'; // .js 확장자에 유의하세요! import express from 'express'; // npm 패키지에 대한 일반 가져오기 const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
변환 중 중요 고려 사항:
- 파일 확장자: 로컬 ES 모듈을 가져올 때 전체 파일 확장자(예:
./utils.js)를 포함해야 합니다. CommonJS에서는 확장을 자주 생략할 수 있었던 것과는 중요한 차이점입니다. Node.js는 설치된 패키지에 대한 베어 지정자(예:express)를 확인하지만, 상대 또는 절대 파일 경로에 대해서는 그렇지 않습니다. __dirname및__filename: 이 CommonJS 전용 전역 변수는 ES 모듈에서 사용할 수 없습니다.import.meta.url과 Node.js의path및fileURLToPath모듈을 사용하여 기능을 다시 만들 수 있습니다.import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); console.log('Current directory:', __dirname); console.log('Current file:', __filename);- JSON 가져오기: CommonJS에서는
./data.json을require()할 수 있었습니다. ESM에서는 일부 Node.js 버전에서 ({ type: 'json' }와 함께import data from './data.json'을 사용하여) 직접 JSON 가져오기가 여전히 실험적입니다. 더 넓은 호환성을 위해fs.readFile및JSON.parse를 사용하여 JSON 파일을 읽어야 할 수 있습니다. - 동적 가져오기/상호 운용성을 위한
require: ESM 파일에서 CommonJS 모듈을require()해야 하는 경우(예: ESM 버전을 게시하지 않은 레거시 라이브러리) 동적 가져오기를 위해import()를 사용할 수 있지만 직접require()는 사용할 수 없습니다. 동적 로딩이 없는 간단한 상호 운용성의 경우 종종 패키지 자체에서 ESM 호환성을 제공하거나 래퍼가 필요할 수 있습니다.
3단계: 마이그레이션된 프로젝트 실행
관련 파일을 모두 변환한 후 프로젝트를 다시 실행하세요.
npm start
Node.js 애플리케이션은 이제 완전히 ES 모듈을 사용하여 실행되어야 하며, 새 메시지를 기록하고 올바른 HTTP 응답을 제공해야 합니다.
혼합 모듈 환경 처리
매우 큰 프로젝트이고 한 번에 모든 것을 전환할 수 없거나, 여전히 CommonJS 전용인 모듈(대부분은 이제 이중 게시 또는 ESM 호환 가능하지만)에 종속되어 있다면 어떻게 될까요? 이때 .cjs 및 .mjs 확장이 매우 유용합니다.
예제: ESM 프로젝트에서 CommonJS 파일 유지
type: "module" 프로젝트에서 즉시 변환할 수 없는 legacy.cjs (CommonJS 파일)가 있다고 상상해 보세요.
legacy.cjs:
// legacy.cjs module.exports = function () { return "This is a legacy CommonJS function!"; };
ESM 파일에서 이를 가져올 수 있습니다.
app.js (.cjs 가져오기용으로 수정됨):
import { add, subtract } from './utils.js'; import express from 'express'; import legacyFunc from './legacy.cjs'; // Node.js는 .cjs 확장자 때문에 이것이 CJS임을 알고 있습니다. const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); console.log('Legacy:', legacyFunc()); // 출력: Legacy: This is a legacy CommonJS function! app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
이것은 type: "module"이 기본값을 설정하지만, .cjs 및 .mjs는 전환 중 혼합 코드를 관리하거나 특정 사용 사례를 위한 필수적인 탈출구를 제공함을 보여줍니다.
결론
package.json에서 "type": "module"을 설정하여 Node.js 프로젝트를 CommonJS에서 ES 모듈로 마이그레이션하는 것은 JavaScript 코드베이스를 현대화하는 중요한 단계입니다. require/module.exports를 import/export로 체계적으로 변환하고 파일 확장자, __dirname, __filename을 신중하게 처리해야 하지만, 광범위한 JavaScript 생태계와의 일치, 개선된 도구, 미래 호환성의 이점은 모두 노력할 가치가 있습니다. 더 깨끗하고 유지 관리가 쉬우며 현대적인 Node.js 애플리케이션을 위해 ES 모듈을 채택하세요. 이 작은 package.json 변경은 Node.js에서 모듈화된 JavaScript의 미래를 열어줍니다.